package br.usp.pcs.basys2.engine;

import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Queue;

import br.usp.pcs.basys2.DutyGenerator;
import br.usp.pcs.basys2.event.Event;
import br.usp.pcs.basys2.event.StartJobEvent;
import br.usp.pcs.basys2.info.JobInfo;

public class Engine {

	private static Engine singleInstance;
	private BasysSystem system;
	private static Queue<Event> events = new PriorityQueue<Event>();
	private static Calendar currentDate = Calendar.getInstance();
	public static DutyGenerator generator = new SimpleGenerator();
	private FlowManager flowManager = new FlowManager();
	private OutputDataGenerator outputDataGenerator = new OutputDataGenerator();
	private SimulationOutput output = new SimulationOutput();
	private OutputBlock currentOutputBlock = new OutputBlock(null);

	public SimulationOutput getOutput() {
		return output;
	}

	public static DutyGenerator getGenerator() {
		return generator;
	}

	private Engine() {
		currentDate.setTimeInMillis(0L);
	}

	void schedule(Event e) {
		if (e.getDateOfArrival().before(currentDate)){
			throw new IllegalArgumentException("Evento nao pode ser agendado no passado."+ e);
		}
		String effect = "Scheduled " + e;
		Engine.getInstance().getCurrentOutputBlock().add(effect);
		events.add(e);
	}

	public void run() {
		currentDate.setTimeInMillis(0L);
		while (!events.isEmpty()) {
			Event nextEvent = events.poll();
			currentDate = nextEvent.getDateOfArrival();
			currentOutputBlock = new OutputBlock(nextEvent.getInfo());
			output.add(currentOutputBlock);
			nextEvent.execute();
		}
	}

	public static Engine getInstance() {
		if (singleInstance==null){
			singleInstance = new Engine();
		}
		return singleInstance;
	}

	public OutputDataGenerator getOutputDataGenerator() {
		return outputDataGenerator;
	}

	public void setSystem(BasysSystem system) {
		this.system = system;
		flowManager.defineSystemFlow(system);
		outputDataGenerator.addSystemListeners(system);
	}

	public void load(List<JobInfo> jobs) {
		for (JobInfo job : jobs) {
			Calendar arrivalDate = job.getArrivalDate();
			Event e = new StartJobEvent(job, arrivalDate);
			this.schedule(e);
		}
	}

	public void setFlow(FlowManager flow) {
		flow.defineSystemFlow(system);
	}
	
	public static Calendar getCalendarAfter(long duration){
		long millis = currentDate.getTimeInMillis()+duration;
		GregorianCalendar result = new GregorianCalendar();
		result.setTimeInMillis(millis);
		return result;
	}
	
	public static Calendar getCurrentDate(){
		return currentDate;
	}

	public BasysSystem getSystem() {
		return system;
	}

	public OutputBlock getCurrentOutputBlock() {
		return currentOutputBlock;
	}

}
